import os, sys, tempfile, zipfile
from pydonet.packet import Packet
from pydonet.message import Message
import optparse

def parse_args():
  parser = optparse.OptionParser()

  parser.add_option('-f', '--filter')
  parser.add_option('-o', '--output-dir', default='.')
  parser.add_option('-z', '--zip', action='store_true',
      help='Read packets compressed with Zip.')
  parser.add_option('-v', '--verbose', action='store_true')
  parser.add_option('-k', '--keep', action='store_true',
      help='Do not remove source packets.')

  CFG, args = parser.parse_args()
  return CFG, args

class StopMessageProcessing (Exception):
  pass

def runfilter(filter, packet, msg):
  state = {
      'disposition': 'keep',
      'messages': []
      }

  def show():
    return 'From: %s, To: %s, Re: %s' % (
        msg.fromUsername, msg.toUsername, msg.subject
    )

  def discard():
    '''Discard a message.'''

    print '  - DISCARD:', show()
    state['disposition'] = 'discard'

  def keep():
    '''Deliver the message normally.  This is the default behavior; you
    would use the keep() command to reverse a previous discard().'''

    print '  - KEEP:', show()
    state['disposition'] = 'keep'

  def stop():
    '''Stop message processing at this point (do not process
    additional rules)'''

    raise StopMessageProcessing()

  def copy(area):
    '''Generate a new copy of the message in the given message
    area.'''

    print '  - COPY (to %s):' % area, show()
    x = Message(data = msg.serialize())
    x.area = area
    state['messages'].append(x)

  def move(area):
    '''Change message area for message.  Needs to change MSGID to
    avoid dupe filters.'''

    print '  - MOVE (to %s):' % area, show()
    msg.klines['X-ORIGINAL-AREA:'] = msg.area
    msg.area = area

  try:
    fd = open(filter)
    exec fd in {
        'M': msg, 'P': packet,
        'discard': discard,
        'copy': copy,
        'move': move,
        'keep': keep,
        'stop': stop,
        'show': show,
        }
  except StopMessageProcessing:
    pass

  return state

def processOneMessage(msg, pkt, pktFd):
  '''Run one message through the filter rules.  Returns the number
  of messages written out (which may be > 0 in the event of `copy()`
  operations).'''

  ocount = 0
  area = msg.area and '[%s] ' % msg.area or ''

  if CFG.verbose:
    print '  %s%s -> %s: %s' % (area, msg.fromUsername, msg.toUsername, msg.subject)

  state = runfilter(CFG.filter, pkt, msg)
  if state['disposition'] == 'keep':
    ocount += 1
    os.write(pktFd, msg.serialize())

  # Write out any new messages that were generated by copy()
  # operations in the filter.
  for msg in state['messages']:
    os.write(pktFd, msg.serialize())
    ocount += 1

  return ocount

def processOnePacket(pkt, name):
  print '+ Processing:', name

  icount = 0
  ocount = 0
  (pktFd, pktPath) = tempfile.mkstemp(dir = CFG.output_dir)

  try:
    os.write(pktFd, pkt.serialize())

    for msg in pkt:
      icount += 1
      ocount += processOneMessage(msg, pkt, pktFd)

    os.write(pktFd, '\x00')
    os.close(pktFd)
  except Exception, detail:
    os.unlink(pktPath)
    raise(detail)

  print '  %d messages in, %d messages out.' % (icount, ocount)

  if ocount > 0:
    os.rename(pktPath, os.path.join(CFG.output_dir, name))
  else:
    print '  Removing %s (no messages)' % name
    os.unlink(pktPath)

def processCompressedPacket(path):
  z = zipfile.ZipFile(path)
  for f in z.namelist():
    pkt = Packet(data = z.read(f))
    processOnePacket(pkt, f)

def processUncompressedPacket(path):
  pkt = Packet(file = path)
  processOnePacket(pkt, os.path.basename(path))

def dispatchPacket(path):
  if CFG.zip:
    processCompressedPacket(path)
  else:
    processUncompressedPacket(path)

  if not CFG.keep:
    try:
      os.unlink(path)
    except OSError, detail:
      print >>sys.stderr, 'WARNING: Failed to delete %s: %s' % (path, detail)

def main():
  global CFG

  CFG, args = parse_args()

  if CFG.filter is None:
    print >>sys.stderr, 'ERROR: You must specify a filter.'
    sys.exit(2)

  if not os.path.isfile(CFG.filter):
    print >>sys.stderr, 'ERROR: Filter "%s" does not exist.' % CFG.filter
    sys.exit(1)

  if not os.path.isdir(CFG.output_dir):
    print >>sys.stderr, 'ERROR: Directory "%s" does not exist.' % CFG.output_dir
    sys.exit(1)

  for arg in args:
    try:
      dispatchPacket(arg)
    except IOError, detail:
      print >>sys.stderr, 'WARNING: Unable to read from %s: %s' % (arg, detail)

